home *** CD-ROM | disk | FTP | other *** search
- /*
- * make.c An imitation of the Unix MAKE facility
- *
- * 88-10-01 v1.0 created by greg yachuk, placed in the public domain
- * 88-10-06 v1.1 changed prerequisite list handling
- * 88-11-11 v1.2 fixed some bugs and added environment variables
- *
- */
-
- #include <stdio.h>
- #include <errno.h>
- #include <fcntl.h>
- #include <string.h>
- #include <memory.h>
- #include <sys/types.h>
- #include <sys/stat.h>
- #include <time.h>
- #ifdef MSDOS
- #include <stdlib.h>
- #include <process.h>
- #include <dos.h>
- #else
- #include <sys/errno.h>
- #include <sys/wait.h>
- #endif
-
- #include "make.h"
- #include "tstring.h"
- #include "decl.h"
-
-
- #ifdef MSDOS
- #define PATH_SEPARATOR ";"
- #define FILE_SEPARATOR "/\\"
- #else
- #define PATH_SEPARATOR ":"
- #define FILE_SEPARATOR "/"
- #endif
-
- #define MAKEINI "default.mk"
-
- char *shell_cmds[] = {
- #ifdef MSDOS
- "dir", "type", "rem", "pause", "date", "time", "ren", "rename",
- "ver", "vol", "break", "verify", "mkdir", "md", "exit", "ctty",
- "echo", "if", "cls", "chdir", "cd", "rmdir", "rd", "copy", "del",
- "erase",
- #endif
- NULL,
- };
-
-
- targptr target_list = NULL; /* list of target nodes */
- fileptr file_list = NULL; /* list of file nodes */
- symptr symbol_list = NULL; /* list of symbol nodes */
- shellptr shell_list = NULL; /* list of shell nodes */
-
- int make_level = 0; /* for counting new_make()'s */
-
- targptr first_targ = NULL; /* first target, in case nothing explicit */
- targptr suffix_targ = NULL; /* .SUFFIXES target pointer */
-
- char **tlist = NULL; /* command line targets */
- char **flist = NULL; /* command line make files */
- char **mlist = NULL; /* command line macros */
-
- int tmax = 0; /* max size of tlist */
- int fmax = 0; /* max size of flist */
- int mmax = 0; /* max size of mlist */
-
- int depend = 0; /* -d option */
- int display = 0; /* -D option */
- int envirn = 0; /* -e option */
- int ignore = 0; /* -i option */
- int noexec = 0; /* -n option */
- int readdef = 1; /* -r option */
- int silent = 0; /* -s option */
- int touch = 0; /* -t option */
-
- int dispcount = 0; /* used for -D option */
-
- long now; /* time at startup */
- char *makeflags; /* value to update the MAKEFLAGS macro with */
-
-
- main(argc, argv)
- int argc;
- char **argv;
- {
- REGISTER int i;
- REGISTER targptr targp;
- symptr symp;
- char *envp;
- char **envv;
-
- /* initialize the various global lists */
-
- depend = 0;
- dispcount = 0;
-
- target_list = NULL;
- file_list = NULL;
- shell_list = NULL;
-
- /* allocate space for command line targets, files and macros */
-
- tlist = grow_list(NULL, &tmax);
- flist = grow_list(NULL, &fmax);
- mlist = grow_list(NULL, &mmax);
-
- /* process MAKEFLAGS environment variable, first */
-
- symp = get_symbol("MAKEFLAGS", 0);
- if (symp->svalue != NULL)
- {
- /* chop up the MAKEFLAGS and feed them to to make_args() */
-
- envp = tstrcpy(symp->svalue);
- envv = tokenize(envp);
- for (i=0; envv[i] != NULL; i++);
- make_args(i, envv);
-
- /* free the vector of pointers, and the string itself, */
- /* since you cannot have macros, targets or makefiles */
- /* in the MAKEFLAGS macro. */
-
- tfree(envv);
- tfree(envp);
- tfree(makeflags); /* ignore this, since we just read it */
- }
-
- make_args(--argc, ++argv); /* process command line options */
-
- add_macro(makeflags, 0); /* update the MAKEFLAGS macro */
- tfree(makeflags);
-
- /* add command line macros, so they DON'T get overridden */
-
- for(i = 0; mlist[i] != NULL; i++)
- add_macro(mlist[i], 1);
-
- tfree(mlist); /* all done with macros */
-
- if (noexec)
- silent = touch = 0; /* -n always displays; never touches */
-
- if (dispcount > 1)
- display = 1; /* display `default.mk' on -DD */
-
- first_targ = NULL; /* used in parse() */
-
- if (readdef)
- parse(fopenp(MAKEINI, "r")); /* read in `default.mk' */
-
- if (dispcount > 0)
- display = 1; /* display makefile's on -D */
-
- first_targ = NULL; /* get first target in `makefile' */
-
- /* parse the makefiles given on command line */
- for(i = 0; flist[i] != NULL; i++)
- {
- parse(equal(flist[i],"-") ? fdopen(dup(fileno(stdin)), "r")
- : fopen(flist[i], "r"));
- }
-
- /* no makefiles specified, so use "makefile" */
- if (i == 0)
- parse(fopen("makefile", "r"));
-
- tfree(flist); /* all done with makefile's */
-
- if ((targp = get_target(".INIT")) != NULL)
- build(targp->tshell); /* process the .INIT rule */
-
- for (i = 0; tlist[i] != NULL; i++)
- make(tlist[i], 1); /* process command line targets */
-
- tfree(tlist); /* all done with targets */
-
- /* if no targets specified, make the first one */
- if (i == 0 && first_targ)
- make(first_targ->tfile->fname, 1);
-
- if ((targp = get_target(".DONE")) != NULL)
- build(targp->tshell); /* process the .DONE rule */
-
- return (0); /* not exit(); see new_make() */
- }
-
-
- /*
- * make_args - process the command line arguments
- */
- make_args(argc, argv)
- int argc;
- char **argv;
- {
- REGISTER int tlen;
- REGISTER int flen;
- REGISTER int mlen;
- char *tmf;
-
- now = time(NULL); /* get current date & time */
-
- makeflags = tstrcpy("MAKEFLAGS+=");
-
- tlen = flen = mlen = 0;
-
- for (;argc != 0; ++argv, --argc)
- {
- if (**argv != '-')
- {
- /* doesn't start with '-'; must be macro or target */
-
- if (strchr(*argv, '='))
- { /* store as a macro */
- if (mlen == mmax)
- mlist = grow_list(mlist, &mmax);
- mlist[mlen++] = *argv;
- }
- else
- { /* store as a target */
- if (tlen == tmax)
- tlist = grow_list(tlist, &tmax);
- tlist[tlen++] = *argv;
- }
- continue;
- }
-
- /* must be an option */
-
- tmf = tstrcat(makeflags, *argv);
- tfree(makeflags);
- makeflags = tstrcat(tmf, " ");
- tfree(tmf);
-
- while (*argv && *++*argv)
- {
- switch (**argv)
- {
- case 'd': /* show dependencies */
- depend++;
- break;
-
- case 'D': /* display makefiles */
- dispcount++;
- break;
-
- case 'e': /* don't override environment */
- envirn = 1;
- break;
-
- case 'f': /* new makefile name */
- if (argc < 2)
- usage();
- if (flen == fmax)
- flist = grow_list(flist, &fmax);
- ++argv, --argc;
- flist[flen++] = *argv;
-
- /* save, but ignore, the makefile */
- tmf = tstrcat(makeflags, *argv);
- tfree(makeflags);
- makeflags = tstrcat(tmf, " ");
- tfree(tmf);
-
- *argv = NULL;
- break;
-
- case 'i': /* ignore errors */
- ignore = 1;
- break;
-
- case 'n': /* don't execute commands */
- noexec = 1;
- break;
-
- case 'r': /* don't read default.mk */
- readdef = 0;
- break;
-
- case 's': /* don't echo commands */
- silent = 1;
- break;
-
- case 't': /* touch files, don't build */
- touch = 1;
- break;
-
- default:
- usage(); /* never returns */
- }
- }
- }
-
- /* terminate all lists with a NULL pointer */
-
- tlist[tlen] = NULL;
- flist[flen] = NULL;
- mlist[mlen] = NULL;
-
- /* let the caller update the makeflags macro */
- }
-
-
- /*
- * grow_list - expand the list of pointers by a factor of two
- */
- char **
- grow_list(list, len)
- char **list;
- int *len;
- {
- REGISTER int l;
-
- l = *len; /* get current length */
-
- /* if list is NULL, start off with a default list */
-
- if (list == NULL)
- list = (char **) talloc(((l=1)+1) * sizeof(char *));
- else
- list = (char **) trealloc((char *)list,
- ((l <<=1)+1)*sizeof(char *));
-
- if (list == NULL)
- terror(1, "too many options");
-
- /* if we are initially allocating it, set first pointer to NULL */
-
- if (l == 1)
- *list = NULL;
-
- *len = l; /* update current length */
- return (list);
- }
-
-
- /*
- * fopenp - open file in current directory or along PATH
- */
- FILE *
- fopenp(fname, type)
- char *fname;
- char *type;
- {
- REGISTER int len;
- REGISTER char *fpath;
- FILE *fd;
- char *path;
- char *tp;
-
- /* try to open file relative to current directory */
- if ((fd = fopen(fname, type)) != NULL)
- return (fd);
-
- /* didn't work, search along path */
-
- if ((path = getenv("PATH")) == NULL)
- return (NULL);
-
- path = tstrcpy(path); /* allocate string and copy */
- fpath = talloc(strlen(path) + strlen(fname) + 2);
-
- /* look for tokens separated by semi-colons (;) or colons (:) */
-
- tp = token(path, PATH_SEPARATOR);
- while (tp != NULL)
- {
- strcpy(fpath, tp);
- len = strlen(fpath) - 1;
-
- /* make sure there is a separator between path and filename */
-
- if (!strchr(FILE_SEPARATOR, fpath[len]))
- fpath[++len] = '/';
-
- strcpy(&fpath[len+1], fname); /* attach the filename */
- if ((fd = fopen(fpath, type)) != NULL)
- break;
-
- tp = token(NULL, PATH_SEPARATOR);
- }
-
- tfree(path);
- tfree(fpath);
-
- return (fd);
- }
-
-
- /*
- * make - guts of the make command
- * - make all pre-requisites, and if necessary, build target
- *
- * returns -1 target was already up to date w.r.t. pre-requisites
- * 0 target has not been built
- * 1 target is now built (and up to date)
- */
- make(targname, worry)
- char *targname;
- int worry; /* if set, it is an error to NOT build this */
- {
- REGISTER targptr targp;
- REGISTER fileptr *preqp;
- fileptr filep;
- long targtime;
- long preqtime;
-
- /* if recorded time of file is not default, we've already built it */
- filep = get_file(targname);
- if (filep && filep->ftime != MAXNEGTIME)
- return (-1);
-
- targp = get_target(targname); /* find the target node */
- if (targp == NULL)
- return (default_rule(targname, worry, 0));
-
- targtime = file_time(targname); /* keep actual time of current target */
-
- /* must build non-existant files, even with no pre-requisites */
- preqtime = MAXNEGTIME + 1;
-
- /* make all pre-requisites */
- preqp = targp->tpreq;
- while (preqp && *preqp)
- {
- make((*preqp)->fname, worry);
-
- /* keep track of newest pre-requisite */
- if (preqtime < (*preqp)->ftime)
- preqtime = (*preqp)->ftime;
-
- /* display as necessary */
- if (depend > 1 || (depend && (*preqp)->ftime > targtime))
- {
- display_prereq(targname, targtime, (*preqp)->fname,
- (*preqp)->ftime);
- }
-
- ++preqp;
- }
-
- if (targp->tshell == NULL) /* try default rules anyway */
- return (default_rule(targname, 0, preqtime > targtime));
- else if (preqtime > targtime)
- {
- if (touch) /* won't be set when `noexec' */
- touch_file(targname);
- else
- {
- add_metas("", "", targname);
- build(targp->tshell);
- }
-
- targp->tfile->ftime = (noexec) ? now : file_time(targname);
- return (1);
- }
-
- targp->tfile->ftime = targtime;
-
- return (-1);
- }
-
-
- /*
- * default_rule - try the .SUFFIXES when we don't have an explicit target
- * - if `worry' is set, it is an ERROR to NOT build this target
- * - `mustbuild' is set if make() has out-of-date prereq's
- * but no explicit shell rules
- */
- default_rule(targname, worry, mustbuild)
- char *targname;
- int worry;
- int mustbuild;
- {
- REGISTER targptr targp;
- REGISTER fileptr *preqp;
- fileptr filep;
- char *ext;
- char *basename;
- char *preqname;
- long targtime;
- long preqtime;
- int built;
- char suffrule[80];
-
- ext = strrchr(targname, '.'); /* find the extension */
- if (ext == NULL)
- ext = targname + strlen(targname);
-
- basename = tstrncpy(targname, ext - targname); /* find the base name */
-
- targtime = file_time(targname);
-
- /* suffix_targ is used to (slightly) speed up this function */
- preqp = suffix_targ ? suffix_targ->tpreq : NULL;
- built = 0;
-
- while (preqp && *preqp && !built)
- {
- /* look for a default rule from SUFFIX to `ext' */
- strcat(strcpy(suffrule, (*preqp)->fname), ext);
- targp = get_target(suffrule); /* e.g. `.c.o' */
-
- if (targp != NULL)
- {
- /* found a rule; see if file exists */
- preqname = tstrcat(basename, (*preqp)->fname);
- preqtime = file_time(preqname);
-
- /*
- * don't bother recursive makes unless necessary
- * e.g. we have .c.o and .l.c, but also .l.o!
- * we want to use .l.o if a .c file does not exist
- */
- if (preqtime != MAXNEGTIME || mustbuild)
- built = make(preqname, 0);
-
- /* check if pre-req file exists and is newer */
- preqtime = file_time(preqname);
- if (preqtime > targtime || (mustbuild && built))
- {
- if (depend)
- {
- display_prereq(targname, targtime,
- preqname, preqtime);
- }
-
- if (touch) /* won't be set when `noexec' */
- touch_file(targname);
- else
- {
- add_metas(basename, preqname, targname);
- build(targp->tshell);
- }
- built = 1;
- }
- else if (depend > 1 && preqtime != MAXNEGTIME)
- {
- display_prereq(targname, targtime,
- preqname, preqtime);
- }
-
- tfree(preqname);
- }
-
- ++preqp; /* try next .SUFFIXES rule */
- }
-
-
- if (!built)
- {
- /* didn't find anything; try the default rule */
- targp = get_target(".DEFAULT");
- if (targp != NULL)
- {
- add_metas(basename, "", targname);
- build(targp->tshell);
- built = 1;
- }
- else if (targtime == MAXNEGTIME && worry)
- terror(1, tstrcat("Don't know how to make ", targname));
- }
-
- tfree(basename);
-
- /* record the current file time */
- if ((filep = get_file(targname)) != NULL)
- {
- filep->ftime = (built == 1 && noexec) ? now
- : file_time(targname);
- }
-
- return (built ? built : ((targtime == MAXNEGTIME) ? 0 : -1));
- }
-
-
- /*
- * add_metas - add symbols for $*, $< and $@
- */
- add_metas(basename, preqname, targname)
- char *basename;
- char *preqname;
- char *targname;
- {
- add_symbol("*", basename, 0);
- add_symbol("<", preqname, 0);
- add_symbol("@", targname, 0);
- }
-
-
- /*
- * touch_file - set the MODIFICATION time of the file to NOW
- */
- touch_file(targname)
- char *targname;
- {
- REGISTER int handle;
- #ifndef MSDOS
- time_t timep[2];
-
- time(&timep[0]);
- timep[1] = timep[0];
- handle = utime(targname, timep);
- #else
- handle = utime(targname, NULL);
- #endif
- fputs("touch ", stdout);
- puts(targname);
-
- if (handle == 0)
- return;
-
- /* create the file, if it did not exist */
- if (errno == ENOENT)
- {
- handle = open(targname, O_CREAT | O_TRUNC, S_IWRITE);
- if (handle != -1)
- {
- close(handle);
- return;
- }
- }
-
- perror("touch");
- exit (1);
- }
-
-
- /*
- * build - process the shell commands
- */
- build(shellp)
- REGISTER shellptr *shellp;
- {
- int runst; /* exec return status */
- char *scmd; /* command with symbols broken out */
- char *tcmd; /* copy of scmd for `tokenize' */
- REGISTER char **argv; /* argified version of scmd */
- char **shcp; /* pointer to shell command list */
-
- if (shellp == NULL)
- return;
-
- for (;*shellp != NULL;
- tfree(scmd), tfree(tcmd), tfree(argv), ++shellp)
- {
- /* breakout runtime symbols (e.g. $* $@ $<) */
- scmd = breakout((*shellp)->scmd);
-
- if (!(*shellp)->s_silent && !silent)
- puts(scmd);
-
- /* make a copy because tokenize litters '\0's */
- tcmd = tstrcpy(scmd);
- argv = tokenize(tcmd);
-
- if (equal(argv[0], "make"))
- {
- /* call ourselves recursively */
- new_make(argv);
- continue;
- }
-
- if (noexec)
- continue;
-
- /* any indirection MUST be handled by the shell */
- if (!(*shellp)->s_shell && strpbrk(scmd, "<|>"))
- (*shellp)->s_shell = 1;
- #ifdef MSDOS
- /* likewise, check for COMMAND.COM builtin commands */
- shcp = shell_cmds;
- for (; !(*shellp)->s_shell && *shcp; ++shcp)
- {
- if (equal(*shcp, argv[0]))
- {
- (*shellp)->s_shell = 1;
- break;
- }
- }
- #endif
- /* run without COMMAND.COM if possible, 'cause it uses RAM */
- if ((*shellp)->s_shell)
- runst = system(scmd);
- else
- runst = spawnvp(P_WAIT, argv[0], argv);
-
- if (runst == 0)
- continue;
-
- /* uh-oh, an error */
- if (runst == -1)
- perror("make");
- #ifdef MSDOS
- terror(0, tstrcat("\007*** Error code ",itoa(runst, scmd, 10)));
- #else
- sprintf(scmd, "\007*** Error code %d\n", runst);
- terror(0, scmd);
- #endif
- if (!ignore && !(*shellp)->s_ignore)
- exit(1);
- }
- }
-
-
- /*
- * new_make - save current environment
- * - call make() recursively (actually main())
- * - clean up new environment
- * - restore environment
- */
- new_make(argv)
- char **argv;
- {
- targptr thead, tnext, tsuffix;
- fileptr fhead, fnext;
- symptr shead, snext;
- shellptr shhead, shnext;
- char **ttlist;
- long tnow;
- int tdepend;
- int i;
-
- /* save all the globals */
- tsuffix = suffix_targ;
- thead = target_list;
- fhead = file_list;
- shead = symbol_list;
- shhead = shell_list;
- ttlist = tlist;
- tnow = now;
- tdepend = depend;
-
- /* count the arguments */
- for (i = 0; argv[i]; ++i);
-
- /* call ourselves recursively; this inherits flags */
- ++make_level;
- main(i, argv);
- --make_level;
-
- /* we're back, so gotta clean up and dispose of a few things */
- while (target_list)
- {
- tnext = target_list->tnext;
- if (target_list->tpreq)
- tfree(target_list->tpreq);
- if (target_list->tshell)
- tfree(target_list->tshell);
- tfree(target_list);
- target_list = tnext;
- }
-
- while (file_list)
- {
- fnext = file_list->fnext;
- tfree(file_list->fname);
- tfree(file_list);
- file_list = fnext;
- }
-
- /* don't drop all symbols, just the new ones */
-
- while (symbol_list != shead)
- {
- snext = symbol_list->snext;
- tfree(symbol_list->sname);
- tfree(symbol_list->svalue);
- tfree(symbol_list);
- symbol_list = snext;
- }
-
- while (shell_list)
- {
- shnext = shell_list->slink;
- tfree(shell_list->scmd);
- tfree(shell_list);
- shell_list = shnext;
- }
-
- /* restore our original globals */
- suffix_targ = tsuffix;
- target_list = thead;
- file_list = fhead;
- symbol_list = shead;
- shell_list = shhead;
- tlist = ttlist;
- now = tnow;
- depend = tdepend;
- }
-
-
- usage()
- {
- puts("make [-f filename] [-dDinrst] [target ...] [macro = value ...]");
- exit(1);
- }
-
-
- display_prereq(targname, targtime, preqname, preqtime)
- char *targname;
- long targtime;
- char *preqname;
- long preqtime;
- {
- #ifdef MSDOS
- char chtime[10];
-
- fputs(targname, stdout);
- fputs(" (", stdout);
- fputs(ltoa(targtime, chtime, 16), stdout);
- fputs((targtime<=preqtime) ? ") older than ":") newer than ", stdout);
- fputs(preqname, stdout);
- fputs(" (", stdout);
- fputs(ltoa(preqtime, chtime, 16), stdout);
- puts(")");
- #else
- printf("%s (%08lx) %s than %s (%08lx)\n",
- targname, targtime,
- (targtime < preqtime) ? "older" : "newer",
- preqname, preqtime);
- #endif
- }
-
-
- long
- file_time(fname)
- char *fname;
- {
- struct stat sbuf;
-
- if (stat(fname, &sbuf) != 0)
- return (MAXNEGTIME);
- return (sbuf.st_mtime);
- }
-
-
- #ifndef MSDOS
- int
- spawnvp(mode, path, args)
- int mode;
- char *path;
- char **args;
- {
- int pid = 0;
- union wait waitword;
-
- if (mode != P_OVERLAY)
- pid = fork();
-
- if (pid == 0)
- execvp(path, args);
-
- wait(&waitword);
- return (waitword.w_retcode);
- }
- #endif
-